C++字符串分词 您所在的位置:网站首页 分词 ABC DEF GH JKL MNO C++字符串分词

C++字符串分词

2023-05-30 13:43| 来源: 网络整理| 查看: 265

原文链接:http://www.cppblog.com/db123/archive/2009/05/21/83556.html

一简介

    字符串分词,即按照某一规则,将一个完整的字符串分割为更多的字段。在C库当中,strtok/wcstok提供了类似的功能,C++标准库兼容了C库。C++的stringstream有类似的功能,boost.string_algorithm也有提供类似的泛型算法。另外在boost当中专门提供了boost.tokenizer来做这样的工作,它的实现是对C++泛型设计的一个不错的诠释,当然,它远没有达到完美的程度。Matthew Wilson在它的stlsoft中也提供了类似的组件,stlsoft.string_tokeniser。它们各有各自的特点,接下来我们对此做一些探讨和研究。

 

二 C库

     C库中提供了strtok/wcstok来实现类似的功能,但是它们具有明显的缺点:

         1.  不可重入性。这是因为它用内部的静态变量来保存相关状态。如果C库实现没有考虑TLS的话,则还有竞争条件的问题(更多信息可以参考 Chapter 21: Thread-Local Storage)。

         2.  参数必须是可写入的。

         3.  参数必须是C风格字符串。

         4.  总是跳过空白。

     下面是一个早期字符串函数的例程(改编自Matthew Wilson《Extended STL, Volume 1》 Chapter 27 ):

#include using namespace std; int main() { char str[] = "abc,def;ghi,jkl;;"; char* outer = NULL; char* inner = NULL; for( outer = strtok( str, ";") ; NULL != outer; outer = strtok(NULL, ";") ) { printf( "Outer token: %s\n", outer ); //for( inner = strtok( outer, ","); NULL != inner; inner = strtok( NULL, ",") ) //{ // printf( "Inner token: %s\n", inner ); //} }   return 0; }

     如上面的程序,如果解注释那一段代码将导致工作不正常,也不会达到我们想要的目的,输出可能如下:

Outer token: abc,def Inner token: abc Inner token: def 请按任意键继续. . .

      在Windows下面,Visual C++ 2005起提供了新的安全版函数,在一定程度上可以解决这种问题(UNIX系统下面有strtok_r有类似的功能),因为它们是可重入的。下面是上面例程的升级版:

#include using namespace std; int main() { char str[] = "abc,def;ghi,jkl;;"; char* outer = NULL; char* inner = NULL; char* pOut = NULL; char* pIn = NULL; for( outer = strtok_s( str, ";", &pOut ) ; NULL != outer; outer = strtok_s(NULL, ";", &pOut) ) { printf_s( "Outer token: %s\n", outer ); for( inner = strtok_s( outer, ",", &pIn); NULL != inner; inner = strtok_s( NULL, ",", &pIn ) ) { printf_s( "Inner token: %s\n", inner ); } } return 0; }     在我的机器上输出如下:

szRes: || pTok: szRes: || pTok: szRes: || pTok: szRes: || pTok: 请按任意键继续. . .     但是即便是如此,我们也不能解决它所存在的其它问题,比如刚才提到的 2、 3、 4点。

 

三 C++ stringstream

     这也是一种可以用来分词的方法,但是实际上用的并不多,而且功能也不够强大,而且很多人都不能很好的掌握stringstream,因为我们平时用得太少了。

#include #include #include using namespace std; int main() { stringstream str("abcd efg kk dd " ); string tok; while( getline( str, tok, ' ' ) ) { cout end; while( iStr != end ) { cout iterator_range operator()( ForwardIteratorT Begin, ForwardIteratorT End ) const;      至于这个 Finder内部您要保存什么信息都可以由您自己决定。这和 boost.tokenizer采用的策略也是类似的,因此它们两个的扩展性都是很强的。本来应该多说一些关于 Boost字符串算法库的内容的,因为毕竟 Tr2中有它,但是这不是这个文档的重点。

 

五 boost.tokenizer

     boost.tokenizer是一个专门提供的字符串分词库,它本身由视图容器和一些迭代器以及迭代器视图组成。虽然我认为可能随着Boost字符串算法库的日趋成熟和强大,这个库可能会被拿掉,但是研究和学习它的一些东西还是有一些价值的。

 

5.1 组件

5.1.1 tokenizer

       tokenizer是一个视图容器,它本身并不包含具体的数据,它存在于boost\tokenizer.hpp中。

template < typename TokenizerFunc = char_delimiters_separator, typename Iterator = std::string::const_iterator, typename Type = std::string > class tokenizer TokenizerFunc : 一个符合TokenizerFunc概念的拆分工具。 Iterator : 用于访问每个拆分后数据的迭代器。 Type: 用于保存拆分后的数据。

5.1.2 token_iterator

    token_iterator是一个迭代器,它用于访问我们的拆分后数据,它位于boost\token_iterator.hpp中。

template class token_iterator : public iterator_facade< token_iterator , Type , typename detail::minimum_category< forward_traversal_tag , typename iterator_traversal::type >::type , const Type& >

     这是它的声明,如果您不了解新式迭代器的概念以及模板元编程,可能理解这段代码有一些困难,但是这并不重要。我简单告诉您的就是从iterator_façade派生可以很轻松的得到一些迭代器的行为,同时只需要派生类实现一些必要的成员函数以符合其概念即可。后面的一个模板元过程只是保证我们的迭代器最多只能是前向迭代器,即使我们使用一个随机迭代器来具现化token_iterator也会被当做前向迭代器。通常从iterator_façade派生都需要将类iterator_core_access设置为友元类。

     迭代器 token_iterator 保存了如下数据成员: TokenizerFunc f_; Iterator begin_; Iterator end_; bool valid_; Type tok_;

      它们分别是:分词工具类对象、开始位置、结束位置、有效位以及结果。

     在token_iterator的实现中,这两个函数很重要:

void increment(){ BOOST_ASSERT(valid_); valid_ = f_(begin_,end_,tok_); } const Type& dereference() const { BOOST_ASSERT(valid_); return tok_; }

    他们分别对应了迭代器自增和提领操作。因此我们可以知道提领操作返回的只是一个常引用,并不会有什么太大的开销,而对于自增来说,它的开销取决于拆分工具的实现。更直接的来说就是取决于拆分工具的operator ()的实现。

 

5.1.3 分词工具类(TokenizerFunc)的概念模型

         boost.tokenizer为我们提供了四个内置的工具类,它们分别是:char_separator、escaped_list_separator、offset_separator以及char_delimiters_separator。其中char_delimiters_separator已经被deprecated了,我们应该使用char_separator来代替它。它们的实现都位于boost\token_functions.hpp中。

        在详细介绍这种工具类之前必须描述一下它的模型和概念,因为如果我们要自己DIY一个分词工具类的话,那么就需要符合它的规则。

        首先 TokenizerFunc 在应用中被 tokenizer 和 token_iterator 按值保存,因此它应该是可拷贝构造的,参考源码我们可以发现类似的代码: void assign(Iterator first, Iterator last, const TokenizerFunc& f){ assign(first,last); f_ = f; }

      因此,TokenizerFunc应该是可赋值的。由于不存在友元关系,因此这两个函数应该必须是public的。

      参考tokenizer和token_iterator的实现还可以发现用到它的其它地方,下面是一个摘录:

void initialize(){ if(valid_) return; f_.reset(); valid_ = (begin_ != end_)? f_(begin_,end_,tok_):false; }       因此我们可以推断出 TokenizerFunc应该具有一个 reset的成员函数,它的意义应该是保证迭代器可以用于一个新的分析得以进行。另外还应该具有一个 operator ()的重载,这个重载应该具有三个或者更多的参数,并且支持 3个参数的调用( 其它的参数有默认值)。这至少的三个参数是开始位置、结束位置以及保存分析结果的 tok_,这里的 tok_应该是作为引用传递的。并且这个 operator()总是有一个 bool返回值,如果返回 true则代表分析可以继续;如果返回 false则对迭代器的有效性产生影响。

 

5.2 工具类解析

5.2.1 char_separator

    char_separator可能是我们最常用到的工具了,让我们先学会如何使用它。例子1(摘自boost文档):

#include #include #include using namespace std; #include int main() { std::string str = ";;Hello|world||-foo--bar;yow;baz|"; typedef boost::tokenizer tokenizer; boost::char_separator sep("-;|"); tokenizer tokens(str, sep); for (tokenizer::iterator tok_iter = tokens.begin(); tok_iter != tokens.end(); ++tok_iter) { std::cout


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有